//simplewebserver package implements a simple webserver
//
//which allow user to signin, signup and edit infomation.
//It uses pure go lang to code
//Frameworks used include Bootstrap, jQuery
package simplewebserver

import (
    "fmt"
    "log"
    "bytes"
    "time"
    "strconv"
    "io/ioutil"
    "path/filepath"
    "net/http"
    "html/template"
)

const (
    TIMELAYOUT = "2006-01-02 15:04:05"
    TPLPATH = "./simplewebserver/template"
    WEBSITENAME = "简易Go网站"
    SIGNIN = "登录"
    SIGNUP = "注册"
    LOGOUT = "登出"
    INFO = "用户信息"
    EDIT = "编辑用户"
    SIGNINERROR = "用户名或者密码错误"
    SIGNUPERROR = "用户名或者邮箱已存在"
)

//PageContext is the struct of template data of each page.
//
//Title is the page titile
//HeaderList is a slice of map, stand for links of nav bar.
//Body is main content of page
type PageContext struct{
    Title string
    HeaderList []map[string]string
    Body template.HTML
}

//UserContext is the struct of template data of user pages.
//
//bool fields select the form group of user form,
//including name, password, email, age, birth field group
//which has label and input element,
//and whether edit or static element of each group,
//and submit button.
//string and int fields filled value of input element.
//Response is response text
type UserContext struct{
    NameGroup bool
    PwdGroup bool
    PwdRPTGroup bool
    EmailGroup bool
    AgeGroup bool
    BirthGroup bool
    ButtonGroup bool
    PwdHint bool
    NameEdit bool
    EmailEdit bool
    AgeEdit bool
    BirthEdit bool
    Signin bool
    Signup bool
    Save bool
    Edit bool
    Validation bool
    NameV string
    EmailV string
    AgeV int
    BirthV string
    Response string
}

//ConfigRoute set routes of server
func ConfigRoute() {
    http.HandleFunc("/", mainHandler)
    http.HandleFunc("/signin", signinHandler)
    http.HandleFunc("/signup", signupHandler)
    http.HandleFunc("/logout", logoutHandler)
    http.HandleFunc("/show", showUserHandler)
    http.HandleFunc("/edit", editUserHandler)
    //css
    css := filepath.Join(TPLPATH, "css/")
    http.Handle("/css/", http.StripPrefix("/css/",
        http.FileServer(http.Dir(css))))
}

func StartServer(port string) {
    log.Println("Start simplewebserver")
    log.Fatal(http.ListenAndServe("localhost:"+
        port, nil))
}

//mainTemplate is template of all files
var mainTemplate *template.Template

//global cache data of pages
//init only once when user request page
var htmlCache map[string]template.HTML

//some HeaderList of PageContext
var userLogHeader, userNotLogHeader []map[string]string

//init parses the template files
//
//Here uses nested template:
//holder.html file is the outer template,
//outer one uses PageContext struct;
//user.html file is the inner template,
//inner one uses UserContext struct.
//inner template uses bytes.Buffer as output.
func init() {
    log.Println("Initializing simplewebserver...")
    mainTemplate = template.Must(
        template.ParseFiles(filepath.Join(TPLPATH,
        "holder.html"),
        filepath.Join(TPLPATH, "user.html")))
    htmlCache = make(map[string]template.HTML)
    userLogHeader = []map[string]string{
        {"Url":"/show",
         "Icon":"user",
         "Text":""},
        {"Url":"/logout",
         "Icon":"log-out",
         "Text":LOGOUT}}
    userNotLogHeader = []map[string]string{
        {"Url":"/signin",
         "Icon":"log-in",
         "Text":SIGNIN},
        {"Url":"/signup",
         "Icon":"registration-mark",
         "Text":SIGNUP}}
}

//verifyUser verifies User
//
//verify in cookie and database
func verifyUser(r *http.Request) (string, bool) {
    ck, err := r.Cookie("UserName")
    if err != nil {
        return "", false
    }
    name := ck.Value
    return name, ExistUser(name)
}

//setCookie set cookie function
//
//addTime is the additon time to Now
func setCookie(name, value string, age int,
            addTime time.Duration,
            w http.ResponseWriter) {
    cookie := http.Cookie{
        Name: name,
        Value: value,
        Path: "/",
        MaxAge: age,
        Expires: time.Now().Add(addTime)}
    http.SetCookie(w, &cookie)
    log.Printf("SetCookie: %s=%s; Expires=%s\n",
        cookie.Name, cookie.Value,
        cookie.Expires.Format(TIMELAYOUT))
}

//setUserCookie
//
//set user cookie one hour
func setUserCookie(user string, w http.ResponseWriter) {
    setCookie("UserName", user, 0, time.Hour, w)
}

func serverError(w http.ResponseWriter, err error) {
    http.Error(w, err.Error(),
            http.StatusInternalServerError)
    log.Printf("Server Error: %s\n", err)
}

func fatalError(err error) {
    if err != nil {
        log.Fatalln(err)
    }
}

//mainHandler
//
//main page handler
func mainHandler(w http.ResponseWriter, r *http.Request) {
    if r.URL.Path != "/" {
        http.NotFound(w, r)
        return
    }
    _, exist := htmlCache["mainPage"]
    if !exist {
        body, err := ioutil.ReadFile(filepath.Join(
            TPLPATH, "index.html"))
        fatalError(err)
        htmlCache["mainPage"] = template.HTML(body)
        log.Println("Main page cached.")
    }
    list := []map[string]string{}
    if name, ok := verifyUser(r); ok {
        list = userLogHeader
        list[0]["Text"] = name
        setUserCookie(name, w)
    } else {
        list = userNotLogHeader
    }
    err := mainTemplate.ExecuteTemplate(
        w, "holder.html",
        PageContext{WEBSITENAME, list,
        htmlCache["mainPage"]})
    if err != nil {
        log.Fatalln(err)
    }
}

func signinHandler(w http.ResponseWriter, r *http.Request) {
    var render = func (response string) {
        var signinBuffer bytes.Buffer
        err := mainTemplate.ExecuteTemplate(
            &signinBuffer, "user.html",
            UserContext{NameGroup:true,
            NameEdit:true, PwdGroup:true,
            ButtonGroup:true, Signin:true,
            Validation:true, Response:response})
        if err != nil {
            log.Fatalln(err)
        }
        err = mainTemplate.ExecuteTemplate(
            w, "holder.html",
            PageContext{WEBSITENAME + " - " + SIGNIN,
            []map[string]string{userNotLogHeader[1]},
            template.HTML(signinBuffer.String())})
        if err != nil {
            http.Error(w, err.Error(),
                http.StatusInternalServerError)
        }
    }
    if r.Method == "GET" {
        _, ok := verifyUser(r)
        if ok {
            http.Redirect(w, r, "/", 302)
            return
        }
        render("")
    } else if r.Method == "POST" {
        name := template.HTMLEscapeString(r.FormValue("name"))
        pwd := template.HTMLEscapeString(r.FormValue("pwd"))
        if ok, _ := Login(name, pwd); !ok {
            render(SIGNINERROR)
        } else {
            log.Printf("User login: %s-%s.\n", name, pwd)
            setUserCookie(name, w)
            http.Redirect(w, r, "/", 302)
        }
    }
}

func logoutHandler(w http.ResponseWriter, r *http.Request) {
    setCookie("UserName", "", -1, time.Duration(0), w)
    http.Redirect(w, r, "/", 302)
    ck, _ := r.Cookie("UserName")
    log.Printf("User logout: %s.\n", ck.Value)
}

func signupHandler(w http.ResponseWriter, r *http.Request) {
    var render = func (response string) {
        var signupBuffer bytes.Buffer
        err := mainTemplate.ExecuteTemplate(
            &signupBuffer, "user.html",
            UserContext{NameGroup:true,
            NameEdit:true, PwdGroup:true,
            PwdRPTGroup:true, EmailGroup:true,
            EmailEdit:true, AgeGroup:true,
            AgeEdit:true, BirthGroup:true,
            BirthEdit:true, PwdHint:true,
            ButtonGroup:true, Signup:true,
            Validation:true, Response:response})
        if err != nil {
            log.Fatalln(err)
        }
        err = mainTemplate.ExecuteTemplate(
            w, "holder.html",
            PageContext{WEBSITENAME + " - " + SIGNUP,
            []map[string]string{userNotLogHeader[0]},
            template.HTML(signupBuffer.String())})
        if err != nil {
            log.Fatalln(err)
        }
    }
    if r.Method == "GET" {
        _, ok := verifyUser(r)
        if ok {
            http.Redirect(w, r, "/", 302)
            return
        }
        render("")
    } else if r.Method == "POST" {
        name := template.HTMLEscapeString(r.FormValue("name"))
        pwd := template.HTMLEscapeString(r.FormValue("pwd"))
        email := template.HTMLEscapeString(r.FormValue("email"))
        agestr := template.HTMLEscapeString(r.FormValue("age"))
        age, _ := strconv.Atoi(agestr)
        birth := template.HTMLEscapeString(r.FormValue("birth"))
        user := NewUser(name, pwd, email, age, birth)
        if err := AddUser(user); err != nil {
            render(SIGNUPERROR)
        } else {
            log.Printf("User signup: %s.\n", user)
            http.Redirect(w, r, "/signin", 302)
        }
    }
}

func showUserHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method == "GET" {
        name, ok := verifyUser(r)
        if !ok {
            http.Redirect(w, r, "/", 302)
            return
        }
        user := FindUser(name)
        var showUserBuffer bytes.Buffer
        err := mainTemplate.ExecuteTemplate(
            &showUserBuffer, "user.html",
            UserContext{NameGroup:true,
            NameV:user.name, EmailGroup:true,
            EmailV:user.email, AgeGroup:true,
            AgeV:user.age, BirthGroup:true,
            BirthV:user.birth.Format("2006-01-02"),
            ButtonGroup:true, Edit:true})
        if err != nil {
            log.Fatalln(err)
        }
        err = mainTemplate.ExecuteTemplate(
            w, "holder.html",
            PageContext{WEBSITENAME + " - " + INFO,
            userLogHeader,
            template.HTML(showUserBuffer.String())})
        if err != nil {
            log.Fatalln(err)
        }
    } else if r.Method == "POST" {
        http.Redirect(w, r, "/edit", 302)
    }
}

func editUserHandler(w http.ResponseWriter, r *http.Request) {
    var render = func (response string) {
        name, ok := verifyUser(r)
        if !ok {
            http.Redirect(w, r, "/", 302)
            return
        }
        user := FindUser(name)
        var editUserBuffer bytes.Buffer
        err := mainTemplate.ExecuteTemplate(
            &editUserBuffer, "user.html",
            UserContext{NameGroup:true,
            NameV:user.name, EmailGroup:true,
            EmailV:user.email, AgeGroup:true,
            AgeV:user.age, BirthGroup:true,
            NameEdit:true, EmailEdit:true,
            AgeEdit:true, BirthEdit:true,
            BirthV:user.birth.Format("2006-01-02"),
            ButtonGroup:true, Save:true,
            Validation:true, Response:response})
        if err != nil {
            log.Fatalln(err)
        }
        err = mainTemplate.ExecuteTemplate(
            w, "holder.html",
            PageContext{WEBSITENAME + " - " + EDIT,
            userLogHeader,
            template.HTML(editUserBuffer.String())})
        if err != nil {
            log.Fatalln(err)
        }
    }
    if r.Method == "GET" {
        render("")
    } else {
        name := template.HTMLEscapeString(r.FormValue("name"))
        email := template.HTMLEscapeString(r.FormValue("email"))
        agestr := template.HTMLEscapeString(r.FormValue("age"))
        age, _ := strconv.Atoi(agestr)
        birth := template.HTMLEscapeString(r.FormValue("birth"))
        user := NewUser(name, "", email, age, birth)
        oldName, _ := verifyUser(r)
        if err := EditUser(oldName, user); err != nil {
            render(SIGNUPERROR)
        } else {
            log.Printf("User %s edit to: %s.\n", oldName, user)
            setUserCookie(name, w)
            http.Redirect(w, r, "/", 302)
        }
    }
}

//The follow code is Obsolete way
//to serve HTML, so we never use them.
//Due to hard-code HTML
//Just for demo
type MainPage struct{}

const pageHeader = `<!DOCTYPE html><html><head>
                <title>%v</title></head><body>`
const pageEnd = "</body></html>"

func (m MainPage) ServeHTTP(
        w http.ResponseWriter,
        r *http.Request) {
    if r.URL.Path != "/" {
        http.NotFound(w, r)
        return
    }
    fmt.Fprintf(w, pageHeader+`
        <h2><a href='/'>Home</a></h2>
        <p><a href='/hello'>Hello page
        </a></p>`+pageEnd, "Home")
}

func HelloPage(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, pageHeader+`
        <p><a href='/'>Back Home</a>
        </p><h2>hello, world!
        </h2>`+pageEnd, "Hello")
}

//Following Save and LoadPage functions are never used
//we use database instead
type Page struct {
    Title string
    Body  []byte
}

func (p *Page) Save() error {
    filename := p.Title + ".txt"
    return ioutil.WriteFile(filename, p.Body, 0600)
}

func LoadPage(title string) (*Page, error) {
    filename := title + ".txt"
    body, err := ioutil.ReadFile(filename)
    if err != nil {
        return nil, err
    }
    return &Page{Title: title, Body: body}, nil
}